using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using Db4objects.Db4o;
using Db4objects.Db4o.Linq;

namespace SharedObjects
{
	[DataContract] public class SecuritySystem : ComputerSystemFeature
	{
		[DataMember] public int DifficultyIdentifier { get; set; }
		[DataMember] public int Version { get; set; }
		[DataMember] public int BaseBypassTime { get; set; }
		[DataMember] public string MethodName { get; set; }
		[DataMember] public CredentialType CredentialType { get; set; }

		private IObjectContainer _db;

		/// <summary>
		/// Executes the Method specified in MethodName. Serves as (legit) access of this security system (as opposed to bypassing)
		/// </summary>
		/// <param name="db">reference to IObjectContainer object of this session</param>
		/// <param name="systemId">ID of target system</param>
		/// <param name="methodParameters">parameters required by the method to be called.</param>
		/// <returns>ulong of ComputerSystemAccount, if bypass successful, or null if failed.</returns>
		public ComputerSystemAccount Access(ref IObjectContainer db, ulong systemId, object[] methodParameters)
		{
			if (string.IsNullOrEmpty(MethodName))
				return null;
			MethodInfo mi = typeof(SecuritySystem).GetMethod(MethodName);
			if (mi == null)
				return null;
			_db = db;
			if (mi.ReturnType != typeof(ComputerSystemAccount))
				return null;
			List<object> parameters = new List<object>();
			parameters.Add(systemId);
			parameters.AddRange(methodParameters);
			return (ComputerSystemAccount)mi.Invoke(this, parameters.ToArray());
		}

		[SecuritySystemMethod]
		public ComputerSystemAccount AccessUsernamePassword(ulong systemId, string username, string password)
		{
			ComputerSystemAccount computerSystemAccount;
			try
			{
				computerSystemAccount = (from ComputerSystem cs in _db where cs.Id == systemId
					   from ComputerSystemAccount csa in cs.Accounts
					   from Credential cred in csa.Credentials
					   where
						cred is UsernamePasswordCredential &&
						((UsernamePasswordCredential)cred).Username == username &&
						((UsernamePasswordCredential)cred).Password == password
					   select csa).FirstOrDefault();
			}
			catch
			{
				return null;
			}
			if (computerSystemAccount == null)
				return null;
			return computerSystemAccount;
		}

		[SecuritySystemMethod]
		public ComputerSystemAccount AccessFingerPrint(ulong systemId, ulong credentialFileCopyId)
		{
			return VerifyCredentialFile<FingerprintCredential>(systemId, credentialFileCopyId, FileType.Fingerprint);
		}

		[SecuritySystemMethod]
		public ComputerSystemAccount AccessRetinalScan(ulong systemId, ulong retinalFileId)
		{
			return VerifyCredentialFile<RetinalScanCredential>(systemId, retinalFileId, FileType.Retina);
		}

		[SecuritySystemMethod]
		public ComputerSystemAccount AccessDnaProfile(ulong systemId, ulong dnaFileId)
		{
			return VerifyCredentialFile<DnaCredential>(systemId, dnaFileId, FileType.Dna);
		}

		[SecuritySystemMethod]
		public ComputerSystemAccount AccessPsec(ulong systemId, ulong psecFileId)
		{
			return VerifyCredentialFile<PsecCredential>(systemId, psecFileId, FileType.Psec);
		}

		[SecuritySystemMethod]
		public ComputerSystemAccount AccessTbpa(ulong systemId, ulong tbpaFileId)
		{
			return VerifyCredentialFile<TbpaCredential>(systemId, tbpaFileId, FileType.Psec);
		}

		public bool CreateCredentials(string login, string password, string hostname, out Credential result)
		{
			result = null;
			string filename = login.Replace(" ", "") + "." + hostname;
			switch(CredentialType)
			{
				case CredentialType.UsernamePassword:
					if(string.IsNullOrEmpty(login) || string.IsNullOrEmpty(password))
						return false;
					UsernamePasswordCredential c = new UsernamePasswordCredential();
					c.Id = Guid.NewGuid().ToLong();
					c.Username = login;
					c.Password = password;
					result = c;
					return true;
				case CredentialType.Fingerprint:
					CredentialFile cfileFp = new CredentialFile { Id = Guid.NewGuid().ToLong(), Time = DateTime.Now, Size = 5, Name = filename + ".FPRINT", DisplaySize = 5, Type = FileType.Fingerprint };
					FingerprintCredential fc = new FingerprintCredential {Id = Guid.NewGuid().ToLong(), ReferenceFile = cfileFp};
					result = fc;
					return true;
				case CredentialType.Retinal:
					CredentialFile cfileRs = new CredentialFile { Id = Guid.NewGuid().ToLong(), Time = DateTime.Now, Size = 10, Name = filename + ".RSCAN", DisplaySize = 15, Type = FileType.Retina };
					RetinalScanCredential rc = new RetinalScanCredential { Id = Guid.NewGuid().ToLong(), ReferenceFile = cfileRs };
					result = rc;
					return true;
				case CredentialType.Dna:
					CredentialFile cfileDna = new CredentialFile { Id = Guid.NewGuid().ToLong(), Time = DateTime.Now, Size = 40, Name = filename + ".DNA", DisplaySize = 120, Type = FileType.Dna };
					DnaCredential dnac = new DnaCredential { Id = Guid.NewGuid().ToLong(), ReferenceFile = cfileDna };
					result = dnac;
					return true;
				case CredentialType.Psec:
					CredentialFile cfilePsec = new CredentialFile { Id = Guid.NewGuid().ToLong(), Time = DateTime.Now, Size = 80, Name = filename + ".PSEC", DisplaySize = 10, Type = FileType.Psec };
					PsecCredential psecc = new PsecCredential { Id = Guid.NewGuid().ToLong(), ReferenceFile = cfilePsec };
					result = psecc;
					return true;
				case CredentialType.Tbpa:
					CredentialFile cfileTbpa = new CredentialFile { Id = Guid.NewGuid().ToLong(), Time = DateTime.Now, Size = 240, Name = filename + ".TBPA", DisplaySize = 240, Type = FileType.Tbpa };
					TbpaCredential tbpac = new TbpaCredential { Id = Guid.NewGuid().ToLong(), ReferenceFile = cfileTbpa };
					result = tbpac;
					return true;
				default:
					return false;
			}
		}

		private ComputerSystemAccount VerifyCredentialFile<T>(ulong systemId, ulong credentialFileCopyId, FileType credentialFileType) where T : FileCredential
		{
			ComputerSystem cs = (from ComputerSystem c in _db where c.Id == systemId select c).FirstOrDefault();
			if (cs == null)
				return null;
			//make sure it does have accounts
			if (cs.Accounts == null)
				_db.Activate(cs, 5);
			if (cs.Accounts == null || cs.Accounts.Count == 0)
				return null;

			//get data for credfile copy
			SystemFile copy = (from SystemFile cf in _db where cf.Id == credentialFileCopyId select cf).FirstOrDefault();
			if (copy == null)
				return null;
			if (copy.Type != credentialFileType)
				return null;
			//make sure the original-ID is there
			if (copy.CopyOf == null)
				return null;

			ulong originalId = copy.CopyOf.Value;
			//obtain original credFile
			
#pragma warning disable 168
			var credFiles = (from CredentialFile cf in _db where cf.Type == FileType.Fingerprint select cf).ToList(); //this is for debugging
#pragma warning restore 168

			CredentialFile original = (from CredentialFile cf in _db where cf.Id == originalId select cf).FirstOrDefault();
			if (original == null)
				return null;
			if (original.Type != credentialFileType)
				return null;

			//get associated CSA for this file. Since we're checking a fingerprint secsys, the file must be associated with a fingerprint secsys too.
			ComputerSystemAccount computerSystemAccount = (
				from csa in cs.Accounts
				from cred in csa.Credentials
				where
					cred is T &&								//we only check FingerprintCredentials with this file
					((T)cred).ReferenceFile != null &&			//if it's actually in this CSA
					((T)cred).ReferenceFile.Id == original.Id	//and if it's the same original file
				select csa).FirstOrDefault();

			if (computerSystemAccount == null)
				return null;
			
			//check, that the csa actually belongs to targetsystem
			if (cs.Accounts.Count(it => it.Id == computerSystemAccount.Id) != 1)
				return null;

			return computerSystemAccount;
		}
	}

	[AttributeUsage(AttributeTargets.Method)]
	public class SecuritySystemMethodAttribute : Attribute
	{
	}
}